#ifndef PSQL_H_
#define PSQL_H_

//stdheaders
#include <string>
#include <vector>
#include <iterator>

//Pqxx headers
#include <pqxx/connection>
#include <pqxx/transactor>
#include <pqxx/result>

//project headers
#include "IDatabase.h"
#include "utf8.h"

//boost headers
#include <boost/lexical_cast.hpp>
#include <boost/noncopyable.hpp>
#include <boost/any.hpp>

namespace DB
{
	struct ConversionWrapper
	{
		std::string conValue;
		bool isNumeric;
	};

//------------------------------------------------------------------------------------------------

	//! TransactionInsert
	/*!
		Trieda ktora sluzi na realizaciu operacie insert nad
		databazou.Implementuje vzor funktor, pricom dedi
		od trasactor na realizaciu potrebnych operacii k
		realizacii operacie insert ako tranzakciu
	*/
	class TransactionInsert : public pqxx::transactor<>
	{
		public:

			//! TransactionInsert (Konstruktor)
			/*!
				Sluzi na inicializaciu objektu
				\param std::string meno tabulky
				\param std::vector<std::pair<std::string,boost::any> > &theData data na insert
			*/
			TransactionInsert(std::string theTableName,Datapair &theData);


			//! TransactionInsert (Konstruktor)
			/*!
				Konstruktor sluziaci na inicializaciu objektu v pripade
				ak je uz dopredu vytvorena sql veta na insert
				\param std::string & theSQLinsert sql veta pre insert
			*/
			TransactionInsert(std::string &theSQLinsert);

			//! operator()
			/*!
				Sluzi na implementaciu vzoru funktor.
				Pri volani tejto funkcie(operatoru) sa realizuje tranzakcia.
				\param argument_type & T predstavuje samotnu transakciu
			*/
			void operator()(argument_type &T);


			//! on_comit
			/*!
				Funkcia sa vola v pripade ak transakcia bola uspesna
			*/
			void on_comit(void);

		private:
			//////////////////////////////////////////////////////////////////////////
			//							MEMBER VARIABLES							//
			//////////////////////////////////////////////////////////////////////////
			std::string myTableName;
			std::string mySQLstm;
			Datapair myDatapair;

			//////////////////////////////////////////////////////////////////////////
			//							MEMBER FUNCTIONS							//
			//////////////////////////////////////////////////////////////////////////
			ConversionWrapper convert_any(boost::any &theAny)
			{
				ConversionWrapper _result;

				if(theAny.type() == typeid(int))
				{
					_result.conValue = boost::lexical_cast<std::string>(boost::any_cast<int>(theAny));
					_result.isNumeric = true;
				}
				if(theAny.type() == typeid(bool))
				{
					_result.conValue = boost::lexical_cast<std::string>(boost::any_cast<bool>(theAny));
					_result.isNumeric = false;
				}
				else if(theAny.type() == typeid(char))
				{
					_result.conValue = boost::lexical_cast<std::string>(boost::any_cast<char>(theAny));
					_result.isNumeric = false;
				}
				else if(theAny.type() == typeid(unsigned char))
				{
					_result.conValue = boost::lexical_cast<std::string>(boost::any_cast<unsigned char>(theAny));
					_result.isNumeric = false;
				}
				else if(theAny.type() == typeid(std::string))
				{
					_result.conValue = boost::any_cast<std::string>(theAny);
					_result.isNumeric = false;
				}
				else if(theAny.type() == typeid(std::wstring))
				{
					std::wstring cast_result = boost::any_cast<std::wstring>(theAny);

					utf8::utf16to8(cast_result.begin(),cast_result.end(),std::back_inserter(_result.conValue));

					_result.isNumeric = false;
				}
				else if(theAny.type() == typeid(long))
				{
					_result.conValue = boost::lexical_cast<std::string>(boost::any_cast<long>(theAny));
					_result.isNumeric = true;
				}

				return _result;
			}


			void format_data(std::string &input,ConversionWrapper &data,bool isLast)
			{
				std::string _formated_data;

				if(isLast)
				{
					if(!data.isNumeric)		//nejedna sa o cislo taze netreba to davat do uvodzoviek
					{
						if(!data.conValue.empty())
						{
							escape_string(data.conValue);
							_formated_data = "'" + data.conValue + "'";
							input += _formated_data + ")";
						}
						else
						{
							input += "NULL)";
						}
					}
					else
					{
						if(!data.conValue.empty())
						{
							input += data.conValue + ")";
						}
						else
						{
							input += "0)";
						}
					}
				}
				else	//niesme na konci
				{
					if(!data.isNumeric)		//nejedna sa o cislo taze netreba to davat do uvodzoviek
					{
						if(!data.conValue.empty())
						{
							escape_string(data.conValue);
							_formated_data = "'" + data.conValue + "'";
							input += _formated_data + ",";
						}
						else
						{
							input += "NULL,";
						}
					}
					else
					{
						if(!data.conValue.empty())
						{
							input += data.conValue + ",";
						}
						else
						{
							input += "0,";
						}
					}
				}
			}

			void escape_string(std::string &input)
			{
				std::string tmp;

				for(std::size_t i=0; i < input.size(); ++i)
				{
					if(i == 0)
					{
						if( input[0] == '\'' || input[0] == '\"' )
						{
							tmp.append("\\");
						}
					}
					else
					{

						if( ( input[i] == '\'' || input[i] == '\"' )  && input[i-1] != '\\' )
						{
							//input.insert(++iter2,'\\');
							tmp.append("\\");
						}
					}

					tmp.append(1,input[i]);
				}

				input.clear();
				input = tmp;
			}
	};//end of TransactionInsert

//------------------------------------------------------------------------------------------------

	//! TransactionSelect
	/*!
		Trieda ktora sluzi na realizaciu operacie select nad
		databazou.Implementuje vzor funktor, pricom dedi
		od trasactor na realizaciu potrebnych operacii k
		realizacii operacie select ako tranzakciu
	*/
	class TransactionSelect : public pqxx::transactor<>
	{
		public:
			//! TransactionSelect (Konstruktor)
			/*!
				Sluzi na inicializaciu objektu
				\param std::string &theSQLselect select sql vyraz
			*/
			TransactionSelect(std::string &theSQLselect,std::vector<Datapair> &theResult);

			//! operator()
			/*!
				Sluzi na implementaciu vzoru funktor.
				Pri volani tejto funkcie(operatoru) sa realizuje tranzakcia.
				\param argument_type & T predstavuje samotnu transakciu
			*/
			void operator()(argument_type &T);


			//! on_comit
			/*!
			Funkcia sa vola v pripade ak transakcia bola uspesna
			*/
			void on_comit(void);

			//! GetResult
			/*!
				Funkcia sluzi na navratenie vysledku SQL selectu
				\param void
				\return DB::Datapair
			*/
			std::vector<Datapair> GetResult(void);

		private:
			std::string &mySQLselect;
			std::vector<Datapair> &myResult;
			pqxx::result mySQLresult;

	};//end of TransactionSelect


	//! Psql
	/*!
		Trieda sluzi na vytvorenie a naslednu komunikaciu s
		databazovym serverom PostgreSQL.

		!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
		Nie vsetky operacie nad databazou su implementovane
		Zatial len Select a Insert !
		!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
	*/
	class Psql : public IDatabase, public boost::noncopyable
	{
		public:

			//! Psql (Konstruktor)
			/*!
				Sluzi na inicializaciu objektu a naviazanie spojenia.
				V pripade zlihania sa vyhodi vynimka
				\param std::string theConString retazec specifikujuci parametre spojenia
			*/
			Psql(std::string theConString);


			//! ~Psql (Destruktor)
			/*!
				Uvolni vsetky zdroje
			*/
			~Psql(void);

			//! Insert (public )
			/*!
			Sluzi na vkladanie dat do databazy pre danu tabulku
			\param std::string theTableName nazov tabulky v DB
			\param std::vector<boost::any> theData data na poslanie, pricom par
			je tvoreny nazvom stlpca a data iduce do daneho stlpca
			\return bool true ak data sa ulozili do tabulky v poriadku ,false inak
			*/
			virtual bool Insert(std::string theTableName,std::vector<std::pair<std::string,boost::any> > &theData);


			//! Insert (public )
			/*!
			Sluzi na vykonanie insertu nad databazou pricom samotny insert
			je napisany uzivatelom.
			\param std::string theSQLstr  SQL vyraz
			\return bool true ak insert bol uspesny, false inak
			*/
			virtual bool Insert(std::string &theSQLstr);


			//! Select (public )
			/*!
			Sluzi na vykonanie SQL query nad databazou
			\param std::string & theSQLquery samotna query
			\param std::vector<std::pair<std::string,boost::any> > &theData data ziskane pomocou query
			\return bool true ak uspesne query, false inak
			*/
			virtual bool Select(std::string &theSQLquery,std::vector<Datapair> &theData);



			//! Update (public )
			/*!
			Sluzi na aktualizaciu hodnot v tabulke
			\param std::string theTableName
			\param std::vector<std::pair<std::string,boost::any> > & newData
			\param std::vector<std::pair<std::string,boost::any> > & oldData
			\return bool
			*/
			virtual bool Update(std::string theTableName,std::vector<std::pair<std::string,boost::any> > &newData
				,std::vector<std::pair<std::string,boost::any> > &oldData);



			//! Update (public )
			/*!
			Popis....
			\param std::string & theSQL
			\return bool
			*/
			virtual bool Update(std::string &theSQL);


			//! Delete (public )
			/*!
			Popis....
			\param std::string & theSQL
			\return bool
			*/
			virtual bool Delete(std::string &theSQL);

			//////////////////////////////////////////////////////////////////////////
			//						 PUBLIC STATIC FUNCTIONS						//
			//////////////////////////////////////////////////////////////////////////

			//! CreateConnectionString (public )
			/*!
				Funkcia sluzi na vytvorenie spravne naformatovaneho connection string.
				Ak nieste si isty ako ma spravne vyzerat con.string tak pouzite tuto
				funkciu na vytvorenie retazca ktory potom predate konstruktoru.

				\param std::string theAdress ip adresa db servera (musi byt ip adresa!)
				\param std::string thePort	port na ktorom bezi server
				\param std::string theDB	nazov databazi na ktory sa ma napojit
				\param std::string theUsrname	pouzivatelske meno
				\param std::string thePassw		pouzivatelske heslo
				\return std::string&	navrati formatovany connection string
			*/
			static std::string CreateConnectionString(std::string theAdress,
													  std::string thePort,
													  std::string theDB,
													  std::string theUsrname,
													  std::string thePassw);


		private:
			//////////////////////////////////////////////////////////////////////////
			//							MEMBER VARIABLES							//
			//////////////////////////////////////////////////////////////////////////
			pqxx::connection *myConnection;
			const int myAttemp_count;


			//////////////////////////////////////////////////////////////////////////
			//							MEMBER FUNCTIONS							//
			//////////////////////////////////////////////////////////////////////////


	};//end of Psql
}//end of DB namespace

#endif//end of PSQL_H_
